package org.hackystat.storageservice.db;

import java.io.File;
import java.io.StringReader;
import java.io.StringWriter;
import java.lang.reflect.Constructor;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;

import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBElement;
import javax.xml.bind.Marshaller;
import javax.xml.bind.Unmarshaller;
import javax.xml.datatype.XMLGregorianCalendar;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import javax.xml.transform.stream.StreamSource;

import org.hackystat.sensorbase.resource.projects.jaxb.Invitations;
import org.hackystat.sensorbase.resource.projects.jaxb.Members;
import org.hackystat.sensorbase.resource.projects.jaxb.Project;
import org.hackystat.sensorbase.resource.projects.jaxb.ProjectRef;
import org.hackystat.sensorbase.resource.projects.jaxb.ProjectSummary;
import org.hackystat.sensorbase.resource.projects.jaxb.Spectators;
import org.hackystat.sensorbase.resource.projects.jaxb.UriPatterns;
import org.hackystat.sensorbase.resource.sensordata.SensorDataManager;
import org.hackystat.sensorbase.resource.sensordata.jaxb.SensorData;
import org.hackystat.sensorbase.resource.sensordata.jaxb.SensorDataRef;
import org.hackystat.sensorbase.resource.sensordata.jaxb.SensorDatas;
import org.hackystat.sensorbase.resource.sensordatatypes.jaxb.SensorDataType;
import org.hackystat.sensorbase.resource.sensordatatypes.jaxb.SensorDataTypeRef;
import org.hackystat.sensorbase.resource.projects.jaxb.Properties;
import org.hackystat.sensorbase.resource.projects.jaxb.Property;
import org.hackystat.sensorbase.resource.users.jaxb.User;
import org.hackystat.sensorbase.resource.users.jaxb.UserRef;
import org.hackystat.storageservice.server.Server;

import static org.hackystat.sensorbase.server.ServerProperties.ADMIN_EMAIL_KEY;
import static org.hackystat.sensorbase.server.ServerProperties.DB_IMPL_KEY;
import org.hackystat.utilities.stacktrace.StackTrace;
import org.hackystat.utilities.tstamp.Tstamp;
import org.w3c.dom.Document;

/**
 * Provides an interface to storage for the resources managed by the SensorBase.
 * Currently we have one storage mechanisms: a persistent store which is implemented by
 * an embedded Derby database.
 * @author Philip Johnson
 */
public class DbManager {
  
  /** The chosen Storage system. */
  private DbImplementation dbImpl;

  /** The SensorDataIndex open tag. */
  public static final String sensorDataIndexOpenTag = "<SensorDataIndex>";
  
  /** The SensorDataIndex close tag. */
  public static final String sensorDataIndexCloseTag = "</SensorDataIndex>";

  /** Holds the class-wide JAXBContext, which is thread-safe. */
  private JAXBContext jaxbContext;

  /** The http string identifier. */
  private static final String http = "http";
  /**
   * Creates a new DbManager which manages access to the underlying persistency layer(s).
   * Instantiates the underlying storage system to use. 
   * @param server The Restlet server instance. 
   */
  
  Server server;
  
  public DbManager(Server server) {
	  this.server = server;
    //Defaults to: "org.hackystat.sensorbase.db.derby.DerbyImplementation"
    String dbClassName = server.getServerProperties().get(DB_IMPL_KEY); 
    Class<?> dbClass = null;
    //First, try to find the class specified in the sensorbase.properties file (or the default) 
    try {
      dbClass = Class.forName(dbClassName);
    }
    catch (ClassNotFoundException e) {
      String msg = "DB error instantiating " + dbClassName + ". Could not find this class.";
      server.getLogger().warning(msg + "\n" + StackTrace.toString(e));
      throw new IllegalArgumentException(e);
    }
    // Next, try to find a constructor that accepts a Server as its parameter. 
    Class<?>[] constructorParam = {org.hackystat.storageservice.server.Server.class};
    Constructor<?> dbConstructor = null;
    try {
      dbConstructor = dbClass.getConstructor(constructorParam);
    }
    catch (Exception e) {
      String msg = "DB error instantiating " + dbClassName + ". Could not find Constructor(server)";
      server.getLogger().warning(msg + "\n" + StackTrace.toString(e));
      throw new IllegalArgumentException(e);
    }
    // Next, try to create an instance of DbImplementation from the Constructor.
    Object[] serverArg = {server};
    try {
      this.dbImpl = (DbImplementation) dbConstructor.newInstance(serverArg);
    }
    catch (Exception e) {
      String msg = "DB error instantiating " + dbClassName + ". Could not create instance.";
      server.getLogger().warning(msg + "\n" + StackTrace.toString(e));
      throw new IllegalArgumentException(e);
    }
    this.dbImpl.initialize();
  }

private void setProjectContext(String context) {
	try {
        this.jaxbContext  = 
        	JAXBContext.newInstance(context);
      }
      catch (Exception e) {
        String msg = "Exception during SensorDataManager initialization processing";
        server.getLogger().warning(msg + "\n" + StackTrace.toString(e));
        throw new RuntimeException(msg, e);
      }
}
  
  /**
   * Persists a SensorData instance.  If the Owner/Timestamp already exists in the table, it is
   * overwritten.
   * @param data The sensor data. 
   * @param xmlSensorData The sensor data resource as an XML String.  
   * @param xmlSensorDataRef The sensor data resource as an XML resource reference
   */
  public void storeSensorData(SensorData data, String xmlSensorData, String xmlSensorDataRef) {
    this.dbImpl.storeSensorData(data, xmlSensorData, xmlSensorDataRef);
  }
  
  /**
   * Persists a SensorDataType instance.  If the SDT name already exists in the table, it is
   * overwritten.
   * @param sdt The sensor data. 
   * @param xmlSensorDataType The SDT resource as an XML String.  
   * @param xmlSensorDataTypeRef The SDT as an XML resource reference
   */
  public void storeSensorDataType(SensorDataType sdt, String xmlSensorDataType, 
      String xmlSensorDataTypeRef) {
    this.dbImpl.storeSensorDataType(sdt, xmlSensorDataType, xmlSensorDataTypeRef);
  }
  
  /**
   * Persists a User instance.  If the User email already exists in the table, it is
   * overwritten.
   * @param user The user instance.
   * @param xmlUser The User resource as an XML String.  
   * @param xmlUserRef The User as an XML resource reference
   */
  public void storeUser(User user, String xmlUser, String xmlUserRef) {
    this.dbImpl.storeUser(user, xmlUser, xmlUserRef);
  }
  
  /**
   * Persists a Project instance.  If the Project already exists in the db, it is
   * overwritten.
   * @param project The project instance.
   * @param xmlProject The Project resource as an XML String.  
   * @param xmlProjectRef The Project as an XML resource reference
   */
  public void storeProject(Project project, String xmlProject, String xmlProjectRef) {
    this.dbImpl.storeProject(project, xmlProject, xmlProjectRef);
  }
  
  /**
   * Returns the XML SensorDataIndex for all sensor data.
   * @return An XML String providing an index of all sensor data resources.
   */
  public String getSensorDataIndex() {
    return this.dbImpl.getSensorDataIndex();
  }
  
 
  /**
   * Returns the XML SensorDataIndex for all sensor data for this user. 
   * @param user The User whose sensor data is to be returned. 
   * @return The XML String providing an index of all relevent sensor data resources.
   */
  public String getSensorDataIndex(User user) {
    return this.dbImpl.getSensorDataIndex(user);
  }
  
  /**
   * Returns the XML SensorDataIndex for all sensor data for this user and sensor data type.
   * @param user The User whose sensor data is to be returned. 
   * @param sdtName The sensor data type name.
   * @return The XML Document instance providing an index of all relevent sensor data resources.
   */
  public String getSensorDataIndex(User user, String sdtName) {
    return this.dbImpl.getSensorDataIndex(user, sdtName);
  }
  
  /**
   * Returns the XML SensorDataIndex for all sensor data matching this user, start/end time, and 
   * whose resource string matches at least one in the list of UriPatterns. 
   * @param users The list of users. 
   * @param startTime The start time. 
   * @param endTime The end time. 
   * @param uriPatterns A list of UriPatterns. 
   * @param sdt The SensorDataType of interest, or null if all data of all SDTs should be retrieved.
   * @return The XML SensorDataIndex string corresponding to the matching sensor data. 
   */
  public String getSensorDataIndex(List<User> users, XMLGregorianCalendar startTime, 
      XMLGregorianCalendar endTime, List<String> uriPatterns, String sdt) {
    return this.dbImpl.getSensorDataIndex(users, startTime, endTime, uriPatterns, sdt);
  }
  
  /**
   * Returns the XML SensorDataIndex for all sensor data matching this user, start/end time, and 
   * whose resource string matches at least one in the list of UriPatterns. 
   * @param users The list of users. 
   * @param startTime The start time. 
   * @param endTime The end time. 
   * @param uriPatterns A list of UriPatterns. 
   * @param sdt The SensorDataType of interest, or null if all data of all SDTs should be retrieved.
   * @param tool The tool of interest.
   * @return The XML SensorDataIndex string corresponding to the matching sensor data. 
   */
  public String getSensorDataIndex(List<User> users, XMLGregorianCalendar startTime, 
      XMLGregorianCalendar endTime, List<String> uriPatterns, String sdt, String tool) {
    return this.dbImpl.getSensorDataIndex(users, startTime, endTime, uriPatterns, sdt, tool);
  }
  
  /**
   * Returns the XML SensorDataIndex for all sensor data matching these users, start/end time, and 
   * whose resource string matches at least one in the list of UriPatterns. 
   * Client must guarantee that startTime and endTime are within Project dates, and that 
   * startIndex and maxInstances are non-negative.
   * @param users The users. 
   * @param startTime The start time. 
   * @param endTime The end time. 
   * @param uriPatterns A list of UriPatterns. 
   * @param startIndex The starting index.
   * @param maxInstances The maximum number of instances to return.
   * @return The XML SensorDataIndex string corresponding to the matching sensor data. 
   */
  public String getSensorDataIndex(List<User> users, XMLGregorianCalendar startTime, 
      XMLGregorianCalendar endTime, List<String> uriPatterns, int startIndex, int maxInstances) {
    return this.dbImpl.getSensorDataIndex(users, startTime, endTime, uriPatterns, startIndex,
        maxInstances);
  }  
  
  /**
   * Returns the XML SensorDataIndex for all sensor data for the given user that arrived
   * at the server between the two timestamps.  This method uses the LastMod timestamp
   * rather than the "regular" timestamp, and is used for real-time monitoring of data
   * arriving at the server. 
   * @param user The user whose data is being monitored.
   * @param lastModStartTime  The lastMod startTime of interest. 
   * @param lastModEndTime  The lastMod endTime of interest. 
   * @return The XML SensorDataIndex for the data that arrived between the two timestamps.
   */
  public String getSensorDataIndexLastMod(User user, XMLGregorianCalendar lastModStartTime,
      XMLGregorianCalendar lastModEndTime) {
    return this.dbImpl.getSensorDataIndexLastMod(user, lastModStartTime, lastModEndTime);
  }
  
  /**
   * Returns the XML SensorDataTypeIndex for all sensor data.
   * @return An XML String providing an index of all SDT resources.
   */
  public String getSensorDataTypeIndex() {
    return this.dbImpl.getSensorDataTypeIndex();
  }
  
  /**
   * Returns the XML UserIndex for all Users..
   * @return An XML String providing an index of all User resources.
   */
  public String getUserIndex() {
    return this.dbImpl.getUserIndex();
  }
  
  /**
   * Returns the XML Project Index for all Projects.
   * @return An XML String providing an index of all Project resources.
   */
  public String getProjectIndex() {
    return this.dbImpl.getProjectIndex();
  }
  
  
  /**
   * Returns a ProjectSummary instance constructed for the given Project between the startTime
   * and endTime. This summary provides a breakdown of the number of sensor data instances found
   * of the given type during the given time period.
   * @param users The list of users in this project.
   * @param startTime The startTime
   * @param endTime The endTime.
   * @param uriPatterns The uriPatterns for this project.
   * @param href The URL naming this resource.
   * @return The ProjectSummary instance. 
   */
  public ProjectSummary getProjectSummary(List<User> users, XMLGregorianCalendar startTime, 
      XMLGregorianCalendar endTime, List<String> uriPatterns, String href) {
    return this.dbImpl.getProjectSummary(users, startTime, endTime, uriPatterns, href);
  }
  
  /**
   * Returns a SensorDataIndex representing the "snapshot" of sensor data in the given time 
   * interval for the given sdt and tool (if tool is not null).  The "snapshot" is the set of
   * sensor data with the most recent runtime value during the interval.
   * @param users The list of users in this project.
   * @param startTime The startTime
   * @param endTime The endTime.
   * @param uriPatterns The uriPatterns for this project.
   * @param sdt The sensor data type of the sensor data of interest.
   * @param tool The tool associated with this snapshot, or null if any tool will do.
   * @return The SensorDataIndex with the latest runtime. 
   */
  public String getProjectSensorDataSnapshot(List<User> users, XMLGregorianCalendar startTime, 
      XMLGregorianCalendar endTime, List<String> uriPatterns, String sdt, String tool) {
    return this.dbImpl.getProjectSensorDataSnapshot(users, startTime, endTime, uriPatterns, sdt,
        tool);
  }
  
  /**
   * Returns the SensorData instance as an XML string, or null.
   * @param user The user.
   * @param timestamp The timestamp associated with this sensor data.
   * @return The SensorData instance as an XML string, or null.
   */
  public String getSensorData(User user, XMLGregorianCalendar timestamp) {
    return this.dbImpl.getSensorData(user, timestamp);
  }
  
  /**
   * Returns the SensorDataType instance as an XML string, or null.
   * @param sdtName The name of the SDT to retrieve.
   * @return The SensorDataType instance as an XML string, or null.
   */
  public String getSensorDataType(String sdtName) {
    return this.dbImpl.getSensorDataType(sdtName);
  }
  
  /**
   * Returns the User instance as an XML string, or null.
   * @param email The email address of the User to retrieve.
   * @return The User instance as an XML string, or null.
   */
  public String getUser(String email) {
    return this.dbImpl.getUser(email);
  }
  
  /**
   * Returns the Project instance as an XML string, or null.
   * @param user The User that owns the Project to retrieve.
   * @param projectName The name of the Project to retrieve.
   * @return The Project instance as an XML string, or null.
   */
  public String getProject(User user, String projectName) {
    return this.dbImpl.getProject(user, projectName);
  }
  
  /**
   * Ensures that sensor data with the given user and timestamp no longer exists.
   * @param user The user.
   * @param timestamp The timestamp associated with this sensor data.
   */
  public void deleteSensorData(User user, XMLGregorianCalendar timestamp) {
    this.dbImpl.deleteSensorData(user, timestamp);
  }
  
  /**
   * Ensures that sensor data with the given user no longer exists.
   * @param user The user.
   */
  public void deleteSensorData(User user) {
    this.dbImpl.deleteSensorData(user);
  }
  
  /**
   * Ensures that the SensorDataType with the given name no longer exists.
   * @param sdtName The SDT name.
   */
  public void deleteSensorDataType(String sdtName) {
    this.dbImpl.deleteSensorDataType(sdtName);
  }
  
  /**
   * Ensures that the User with the given email address is no longer present in this db.
   * @param email The user email.
   */
  public void deleteUser(String email) {
    this.dbImpl.deleteUser(email);
  }
  
  /**
   * Ensures that the Project with the given user and name is no longer present in this db.
   * @param user  The User who owns this Project.
   * @param projectName The name of the Project to delete.
   */
  public void deleteProject(User user, String projectName) {
    this.dbImpl.deleteProject(user, projectName);
  }
  
  /**
   * Databases like Derby require an explicit compress command for releasing disk space 
   * after a large number of rows have been deleted.  This operation invokes the compress
   * command on all tables in the database.  If a database implementation does not support
   * explicit compression, then this command should do nothing but return true.   
   * @return True if the compress command succeeded or if the database does not support
   * compression. 
   */
  public boolean compressTables() {
    return this.dbImpl.compressTables();
  }
  
  /**
   * The most appropriate set of indexes for the database has been evolving over time as we 
   * develop new queries.  This command sets up the appropriate set of indexes.  It should be
   * able to be called repeatedly without error. 
   * @return True if the index commands succeeded. 
   */
  public boolean indexTables() {
    return this.dbImpl.indexTables();
  }
  
  /**
   * Returns the current number of rows in the specified table.  
   * @param table The table whose rows are to be counted. 
   * @return The number of rows in the table, or -1 if the table does not exist or an error occurs. 
   */
  public int getRowCount(String table) {
    return this.dbImpl.getRowCount(table);
  }
  
  /**
   * Returns a set containing the names of all tables in this database.  Used by clients to 
   * invoke getRowCount with a legal table name. 
   * @return A set of table names.
   */
  public Set<String> getTableNames() {
    return this.dbImpl.getTableNames();
  }
  
  /**
   * Takes a String encoding of a SensorData in XML format and converts it to an instance. 
   * 
   * @param xmlString The XML string representing a SensorData.
   * @return The corresponding SensorData instance. 
   * @throws Exception If problems occur during unmarshalling.
   */
  public SensorData makeSensorData(String xmlString) throws Exception {
	  setProjectContext("org.hackystat.sensorbase.resource.sensordata.jaxb");
    Unmarshaller unmarshaller = jaxbContext.createUnmarshaller();
    return (SensorData)unmarshaller.unmarshal(new StringReader(xmlString));
  }
  
  /**
   * Takes a String encoding of a SensorDatas in XML format and converts it to an instance. 
   * 
   * @param xmlString The XML string representing a SensorData.
   * @return The corresponding SensorData instance. 
   * @throws Exception If problems occur during unmarshalling.
   */
  public SensorDatas makeSensorDatas(String xmlString) throws Exception {
	  setProjectContext("org.hackystat.sensorbase.resource.sensordata.jaxb");
    Unmarshaller unmarshaller = jaxbContext.createUnmarshaller();
    return (SensorDatas)unmarshaller.unmarshal(new StringReader(xmlString));
  }
  

  /**
   * Converts an "Owner" string to an email address.
   * The owner string might be a URI (starting with http) or an email address. 
   * @param owner The owner string. 
   * @return The email address corresponding to the owner string. 
   */
  public String convertOwnerToEmail(String owner) {
    if (owner.startsWith(http)) {
      int lastSlash = owner.lastIndexOf('/');
      if (lastSlash < 0) {
        throw new IllegalArgumentException("Could not convert owner to URI");
      }
      return owner.substring(lastSlash + 1); 
    }
    // Otherwise owner is already the email. 
    return owner;
  }
  
  /**
   * Updates the Manager with this sensor data. Any old definition is overwritten for
   * this user and timestamp.
   * If runtime is not specified, it is defaulted to the timestamp.
   * If tool, resource, or SDT are not specified, they default to "".
   * @param data The sensor data. 
   */
  public void putSensorData(SensorData data) {
    if (data.getRuntime() == null) {
      data.setRuntime(data.getTimestamp());
    }
    if (data.getTool() == null) {
      data.setTool("");
    }
    if (data.getResource() == null) {
      data.setResource("");
    }
    if (data.getSensorDataType() == null) { 
      data.setSensorDataType("");
    }
    try {
      data.setLastMod(Tstamp.makeTimestamp());
      storeSensorData(data, makeSensorData(data),
          makeSensorDataRefString(data));
    }
    catch (Exception e) {
      server.getLogger().warning("Failed to put sensor data " + StackTrace.toString(e));
    }
  }

  /**
   * Returns the passed SensorData instance as a String encoding of its XML representation.
   * Final because it's called in constructor.
   * @param data The SensorData instance. 
   * @return The XML String representation.
   * @throws Exception If problems occur during translation. 
   */
  public final String makeSensorData (SensorData data) throws Exception {
	  setProjectContext("org.hackystat.sensorbase.resource.sensordata.jaxb");
    Marshaller marshaller = jaxbContext.createMarshaller(); 
    DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
    dbf.setNamespaceAware(true);
    DocumentBuilder documentBuilder = dbf.newDocumentBuilder();
    Document doc = documentBuilder.newDocument();
    marshaller.marshal(data, doc);
    DOMSource domSource = new DOMSource(doc);
    StringWriter writer = new StringWriter();
    StreamResult result = new StreamResult(writer);
    TransformerFactory tf = TransformerFactory.newInstance();
    Transformer transformer = tf.newTransformer();
    transformer.transform(domSource, result);
    String xmlString = writer.toString();
    // Now remove the processing instruction.  This approach seems like a total hack.
    xmlString = xmlString.substring(xmlString.indexOf('>') + 1);
    return xmlString;
  }
  
  /**
   * Returns the passed SensorData instance as a String encoding of its XML representation 
   * as a SensorDataRef object.
   * Final because it's called in constructor.
   * @param data The SensorData instance. 
   * @return The XML String representation of it as a SensorDataRef
   * @throws Exception If problems occur during translation. 
   */
  public final String makeSensorDataRefString (SensorData data) throws Exception {
	  setProjectContext("org.hackystat.sensorbase.resource.sensordata.jaxb");
    SensorDataRef ref = makeSensorDataRef(data);
    Marshaller marshaller = jaxbContext.createMarshaller(); 
    DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
    dbf.setNamespaceAware(true);
    DocumentBuilder documentBuilder = dbf.newDocumentBuilder();
    Document doc = documentBuilder.newDocument();
    marshaller.marshal(ref, doc);
    DOMSource domSource = new DOMSource(doc);
    StringWriter writer = new StringWriter();
    StreamResult result = new StreamResult(writer);
    TransformerFactory tf = TransformerFactory.newInstance();
    Transformer transformer = tf.newTransformer();
    transformer.transform(domSource, result);
    String xmlString = writer.toString();
    // Now remove the processing instruction.  This approach seems like a total hack.
    xmlString = xmlString.substring(xmlString.indexOf('>') + 1);
    return xmlString;
  }
  
  /**
   * Returns a SensorDataRef instance constructed from a SensorData instance.
   * @param data The sensor data instance. 
   * @return A SensorDataRef instance. 
   */
  public SensorDataRef makeSensorDataRef(SensorData data) {
    SensorDataRef ref = new SensorDataRef();
    String email = convertOwnerToEmail(data.getOwner());
    String sdt = convertSdtToName(data.getSensorDataType());
    XMLGregorianCalendar timestamp = data.getTimestamp();
    ref.setOwner(email);
    ref.setSensorDataType(sdt);
    ref.setTimestamp(timestamp);
    ref.setTool(data.getTool());
    ref.setHref(this.server.getHostName() + "sensordata/" + email + "/" +  timestamp.toString()); 
    ref.setLastMod(data.getLastMod());
    return ref;
  }
  
  /**
   * Converts an "sdt" string to the sdt name.
   * The sdt string might be a URI (starting with http) or the sdt name.
   * @param sdt The sdt string. 
   * @return The sdt name corresponding to the sdt string. 
   */
  public String convertSdtToName(String sdt) {
    if (sdt.startsWith(http)) {
      int lastSlash = sdt.lastIndexOf('/');
      if (lastSlash < 0) {
        throw new IllegalArgumentException("Could not convert sdt to name");
      }
      return sdt.substring(lastSlash + 1); 
    }
    // Otherwise sdt is already the name.
    return sdt;
  }
  
  /**
   * Takes a String encoding of a Properties in XML format and converts it to an instance. 
   * 
   * @param xmlString The XML string representing a Properties.
   * @return The corresponding Properties instance. 
   * @throws Exception If problems occur during unmarshalling.
   */
  public final synchronized Properties makeProperties(String xmlString) throws Exception {
	  setProjectContext("org.hackystat.sensorbase.resource.projects.jaxb");
    Unmarshaller unmarshaller = this.jaxbContext.createUnmarshaller();
    return (Properties)unmarshaller.unmarshal(new StringReader(xmlString));
  }
  
  
  /**
   * Takes a String encoding of a SensorDataType in XML format and converts it to an instance. 
   * 
   * @param xmlString The XML string representing a SensorDataType
   * @return The corresponding SensorDataType instance. 
   * @throws Exception If problems occur during unmarshalling.
   */
  public final synchronized SensorDataType makeSensorDataType(String xmlString) throws Exception {
	  setProjectContext("org.hackystat.sensorbase.resource.sensordatatypes.jaxb");
    Unmarshaller unmarshaller = this.jaxbContext.createUnmarshaller();
    return (SensorDataType)unmarshaller.unmarshal(new StringReader(xmlString));
  }
  
  /**
   * Returns the passed SensorDataType instance as a String encoding of its XML representation.
   * Final because it's called in constructor.
   * @param sdt The SensorDataType instance. 
   * @return The XML String representation.
   * @throws Exception If problems occur during translation. 
   */
  public final synchronized String makeSensorDataType (SensorDataType sdt) throws Exception {
	  setProjectContext("org.hackystat.sensorbase.resource.sensordatatypes.jaxb");
    Marshaller marshaller = jaxbContext.createMarshaller(); 
    DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
    dbf.setNamespaceAware(true);
    DocumentBuilder documentBuilder = dbf.newDocumentBuilder();
    Document doc = documentBuilder.newDocument();
    marshaller.marshal(sdt, doc);
    DOMSource domSource = new DOMSource(doc);
    StringWriter writer = new StringWriter();
    StreamResult result = new StreamResult(writer);
    TransformerFactory tf = TransformerFactory.newInstance();
    Transformer transformer = tf.newTransformer();
    transformer.transform(domSource, result);
    String xmlString = writer.toString();
    // Now remove the processing instruction.  This approach seems like a total hack.
    xmlString = xmlString.substring(xmlString.indexOf('>') + 1);
    return xmlString;
  }

  /**
   * Returns the passed SensorDataType instance as a String encoding of its XML representation 
   * as a SensorDataTypeRef object.
   * Final because it's called in constructor.
   * @param sdt The SensorDataType instance. 
   * @return The XML String representation of it as a SensorDataTypeRef
   * @throws Exception If problems occur during translation. 
   */
  public final synchronized String makeSensorDataTypeRefString (SensorDataType sdt) 
  throws Exception {
	  setProjectContext("org.hackystat.sensorbase.resource.sensordatatypes.jaxb");
    SensorDataTypeRef ref = makeSensorDataTypeRef(sdt);
    Marshaller marshaller = jaxbContext.createMarshaller(); 
    DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
    dbf.setNamespaceAware(true);
    DocumentBuilder documentBuilder = dbf.newDocumentBuilder();
    Document doc = documentBuilder.newDocument();
    marshaller.marshal(ref, doc);
    DOMSource domSource = new DOMSource(doc);
    StringWriter writer = new StringWriter();
    StreamResult result = new StreamResult(writer);
    TransformerFactory tf = TransformerFactory.newInstance();
    Transformer transformer = tf.newTransformer();
    transformer.transform(domSource, result);
    String xmlString = writer.toString();
    // Now remove the processing instruction.  This approach seems like a total hack.
    xmlString = xmlString.substring(xmlString.indexOf('>') + 1);
    return xmlString;
  }
  
  /**
   * Returns a SensorDataTypeRef instance constructed from a SensorDataType instance.
   * @param sdt The SensorDataType instance. 
   * @return A SensorDataTypeRef instance. 
   */
  public synchronized SensorDataTypeRef makeSensorDataTypeRef(SensorDataType sdt) {
    SensorDataTypeRef ref = new SensorDataTypeRef();
    ref.setName(sdt.getName());
    ref.setHref(this.server.getHostName() + "sensordatatypes/" + sdt.getName()); 
    return ref;
  }


  
  /**
   * Returns the passed Project instance as a String encoding of its XML representation.
   * Final because it's called in constructor.
   * @param project The Project instance. 
   * @return The XML String representation.
   * @throws Exception If problems occur during translation. 
   */
  public final synchronized String makeProject (Project project) throws Exception {
	setProjectContext("org.hackystat.sensorbase.resource.projects.jaxb");
    Marshaller marshaller = jaxbContext.createMarshaller(); 
    DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
    dbf.setNamespaceAware(true);
    DocumentBuilder documentBuilder = dbf.newDocumentBuilder();
    Document doc = documentBuilder.newDocument();
    marshaller.marshal(project, doc);
    DOMSource domSource = new DOMSource(doc);
    StringWriter writer = new StringWriter();
    StreamResult result = new StreamResult(writer);
    TransformerFactory tf = TransformerFactory.newInstance();
    Transformer transformer = tf.newTransformer();
    transformer.transform(domSource, result);
    String xmlString = writer.toString();
    // Now remove the processing instruction.  This approach seems like a total hack.
    xmlString = xmlString.substring(xmlString.indexOf('>') + 1);
    return xmlString;
  }

  /**
   * Takes a String encoding of a Project in XML format and converts it to an instance. 
   * 
   * @param xmlString The XML string representing a Project
   * @return The corresponding Project instance. 
   * @throws Exception If problems occur during unmarshalling.
   */
  public final synchronized Project makeProject(String xmlString) throws Exception {
	  setProjectContext("org.hackystat.sensorbase.resource.projects.jaxb");
	    //System.out.println("Got here mp.1");
    Unmarshaller unmarshaller = this.jaxbContext.createUnmarshaller();
    //System.out.println("Got here mp.2");
    return (Project)unmarshaller.unmarshal(new StringReader(xmlString));
  }

  /**
   * Ensures that project has default values for Invitations, Members, Properties, 
   * Spectators, and UriPatterns.
   * @param project The project to check.
   * @return The project representation with initialized fields as needed.
   */
  private Project provideDefaults(Project project) {
    if (project.getInvitations() == null) {
      project.setInvitations(new Invitations());
    }
    if (project.getSpectators() == null) {
      project.setSpectators(new Spectators());
    }
    if (project.getMembers() == null) {
      project.setMembers(new Members());
    }
    if (project.getProperties() == null) {
      project.setProperties(new Properties());
    }
    if (project.getUriPatterns() == null) {
      // If missing, default to matching everything. 
      UriPatterns uriPatterns = new UriPatterns();
      uriPatterns.getUriPattern().add("*");
      project.setUriPatterns(uriPatterns);
    }
    if (project.getLastMod() == null) {
      project.setLastMod(Tstamp.makeTimestamp());
    }
    return project;
  }
  
  /**
   * Updates the Manager with this Project. Any old definition is overwritten.
   * Provide default values for UriPatterns, Properties, Members, and Invitations if not provided.
   * @param project The Project.
   */
  public synchronized void putProject(Project project) {
    try {
      provideDefaults(project);
      project.setLastMod(Tstamp.makeTimestamp());
      String xmlProject =  this.makeProject(project);
      String xmlRef =  this.makeProjectRefString(project);
      storeProject(project, xmlProject, xmlRef);
    }
    catch (Exception e) {
      server.getLogger().warning("Failed to put Project" + StackTrace.toString(e));
    }
  }
  

  /**
   * Returns the passed Project instance as a String encoding of its XML representation 
   * as a ProjectRef object.
   * Final because it's called in constructor.
   * @param project The Project instance. 
   * @return The XML String representation of it as a ProjectRef
   * @throws Exception If problems occur during translation. 
   */
  public final synchronized String makeProjectRefString (Project project) 
  throws Exception {
	  setProjectContext("org.hackystat.sensorbase.resource.projects.jaxb");
	    ProjectRef ref = makeProjectRef(project);
    Marshaller marshaller = jaxbContext.createMarshaller(); 
    DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
    dbf.setNamespaceAware(true);
    DocumentBuilder documentBuilder = dbf.newDocumentBuilder();
    Document doc = documentBuilder.newDocument();
    marshaller.marshal(ref, doc);
    DOMSource domSource = new DOMSource(doc);
    StringWriter writer = new StringWriter();
    StreamResult result = new StreamResult(writer);
    TransformerFactory tf = TransformerFactory.newInstance();
    Transformer transformer = tf.newTransformer();
    transformer.transform(domSource, result);
    String xmlString = writer.toString();
    // Now remove the processing instruction.  This approach seems like a total hack.
    xmlString = xmlString.substring(xmlString.indexOf('>') + 1);
    return xmlString;
  }
  
  /**
   * Returns a ProjectRef instance constructed from a Project instance.
   * @param project The Project instance. 
   * @return A ProjectRef instance. 
   */
  public synchronized ProjectRef makeProjectRef(Project project) {
    ProjectRef ref = new ProjectRef();
    String ownerEmail = convertOwnerToEmail(project.getOwner());
    ref.setName(project.getName());
    ref.setOwner(ownerEmail);
    ref.setLastMod(project.getLastMod());
    ref.setHref(this.server.getHostName() + "projects/" + ownerEmail + "/" + project.getName()); 
    return ref;
  }


  /**
   * Creates and returns the list of User instances associated with project.
   * The users are the owner plus all members.
   * If the owner email or member emails cannot be resolved to User instances, they are silently
   * ignored.
   * @param project The project whose users are to be found and returned.
   * @return The list of Users associated with this project.
   */
  public List<User> getProjectUsers(Project project) {
    List<User> users = new ArrayList<User>();
    User owner = null;
	try {
		owner = makeUser(getUser(project.getOwner()));
	} catch (Exception e) {
		// TODO Auto-generated catch block
		e.printStackTrace();
	}
    if (owner != null) {
      users.add(owner);
    }
    for (String member : project.getMembers().getMember()) {
    
    User user = null;
	try {
		user = makeUser(getUser(member));
	} catch (Exception e) {
		// TODO Auto-generated catch block
		e.printStackTrace();
	}
      if (user != null) {
        users.add(user);
      }
    }
    return users; 
  }
  
  /**
   * Takes a String encoding of a User in XML format and converts it to an instance. 
   * 
   * @param xmlString The XML string representing a User
   * @return The corresponding User instance. 
   * @throws Exception If problems occur during unmarshalling.
   */
  public final synchronized User makeUser(String xmlString) throws Exception {
	  setProjectContext("org.hackystat.sensorbase.resource.users.jaxb");
	    Unmarshaller unmarshaller = this.jaxbContext.createUnmarshaller();
    return (User)unmarshaller.unmarshal(new StringReader(xmlString));
  }

  /**
   * Returns the passed ProjectSummary instance as a String encoding of its XML representation. 
   * @param summary The ProjectSummary instance. 
   * @return The XML String representation of it.
   * @throws Exception If problems occur during translation. 
   */
  public final synchronized String makeProjectSummaryString(ProjectSummary summary) 
  throws Exception {
	  setProjectContext("org.hackystat.sensorbase.resource.projects.jaxb");
    Marshaller marshaller = jaxbContext.createMarshaller(); 
    DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
    dbf.setNamespaceAware(true);
    DocumentBuilder documentBuilder = dbf.newDocumentBuilder();
    Document doc = documentBuilder.newDocument();
    marshaller.marshal(summary, doc);
    DOMSource domSource = new DOMSource(doc);
    StringWriter writer = new StringWriter();
    StreamResult result = new StreamResult(writer);
    TransformerFactory tf = TransformerFactory.newInstance();
    Transformer transformer = tf.newTransformer();
    transformer.transform(domSource, result);
    String xmlString = writer.toString();
    // Now remove the processing instruction.  This approach seems like a total hack.
    xmlString = xmlString.substring(xmlString.indexOf('>') + 1);
    return xmlString;
  }
  
  
  /**
   * Returns an XML SensorDataIndex String for all data associated with the Project
   * owned by this user.
   * Assumes that the owner and projectName define an existing Project.
   * @param owner The User that owns this Project.
   * @param project the Project instance.
   * @return The XML SensorDataIndex string providing an index to all data for this project.
   * @throws Exception If things go wrong. 
   */
  public synchronized String getProjectSensorDataIndex(User owner, Project project) 
  throws Exception {
    XMLGregorianCalendar startTime = project.getStartTime();
    XMLGregorianCalendar endTime = project.getEndTime();
    List<String> patterns = project.getUriPatterns().getUriPattern();
    List<User> users = getProjectUsers(project);
    return getSensorDataIndex(users, startTime, endTime, patterns, null);
  }
  

  /**
   * Returns the XML SensorDataIndex string for the data associated with this Project within the 
   * specified start and end times.
   * Assumes that owner, project, startTime, and endTime are non-null, and that startTime and
   * endTime are within the Project start and end time interval.
   * @param owner The User who owns this Project. 
   * @param project the Project.
   * @param startTime The startTime.
   * @param endTime The endTime.
   * @param sdt The SensorDataType of interest, or null if all sensordatatypes are to be retrieved.
   * @return The XML String providing a SensorDataIndex to the sensor data in this project
   * starting at startTime and ending at endTime. 
   * @throws Exception if problems occur.
   */  
  public synchronized String getProjectSensorDataIndex(User owner, 
      Project project, XMLGregorianCalendar startTime, XMLGregorianCalendar endTime, String sdt) 
  throws Exception {
    List<String> patterns = project.getUriPatterns().getUriPattern();
    List<User> users = getProjectUsers(project);
    return getSensorDataIndex(users, startTime, endTime, patterns, sdt);
  }
  
  /**
   * Returns the XML SensorDataIndex string for the data associated with this Project within the 
   * specified start and end times.
   * Assumes that owner, project, startTime, and endTime are non-null, and that startTime and
   * endTime are within the Project start and end time interval.
   * @param owner The User who owns this Project. 
   * @param project the Project.
   * @param startTime The startTime.
   * @param endTime The endTime.
   * @param sdt The SensorDataType of interest, or null if all sensordatatypes are to be retrieved.
   * @param tool The tool of interest.
   * @return The XML String providing a SensorDataIndex to the sensor data in this project
   * starting at startTime and ending at endTime. 
   * @throws Exception if problems occur.
   */  
  public synchronized String getProjectSensorDataIndex(User owner, 
      Project project, XMLGregorianCalendar startTime, XMLGregorianCalendar endTime, String sdt, 
      String tool) 
  throws Exception {
    List<String> patterns = project.getUriPatterns().getUriPattern();
    List<User> users = getProjectUsers(project);
    return getSensorDataIndex(users, startTime, endTime, patterns, sdt, tool);
  }
  
  /**
   * Returns the XML SensorDataIndex string for the data associated with this Project within the 
   * specified start and end times and startIndex and maxInstances.
   * Assumes that owner, project, startTime, and endTime are non-null, and that startTime and
   * endTime are within the Project start and end time interval, and that startIndex and 
   * maxInstances are non-negative.
   * @param owner The User who owns this Project. 
   * @param project the Project.
   * @param startTime The startTime.
   * @param endTime The endTime.
   * @param startIndex The starting index within the timestamp-ordered list of all sensor data
   * instances associated with this project at the time of this call.
   * @param maxInstances The maximum number of instances to return in the index.
   * @return The XML String providing a SensorDataIndex to the sensor data in this project
   * starting at startTime and ending at endTime with the specified startIndex and maxInstances. 
   * @throws Exception if problems occur.
   */  
  public synchronized String getProjectSensorDataIndex(User owner, 
      Project project, XMLGregorianCalendar startTime, XMLGregorianCalendar endTime, int startIndex,
      int maxInstances) 
  throws Exception {
    List<String> patterns = project.getUriPatterns().getUriPattern();
    List<User> users = getProjectUsers(project);
    return getSensorDataIndex(users, startTime, endTime, patterns, startIndex,
        maxInstances);
  }
  
  

  /**
   * Returns true if email is a defined User with Admin privileges. 
   * @param email An email address. 
   * @return True if email is a User with Admin privileges. 
   */
  public synchronized boolean isAdmin(String email) {
    return (email != null) &&
           email.equals(server.getServerProperties().get(ADMIN_EMAIL_KEY));
  }
  }
